#include "csg.h"

CSG::CSG(Node *root) : _root(root)
{
}

const Node *CSG::root() const
{
    return _root;
}

void CSG::setRoot(const Node *root)
{
    _root = root;
}

CSG::~CSG()
{
    delete _root;
}

Node::~Node()
{
    if(_operation == Node::Shape_NodeType)
        delete _shape;
    else
    {
        delete _right;
        delete _left;
    }
}

Node::Node(const Shape *shape):
    _operation(Node::Shape_NodeType),
    _shape(shape),
    _right(nullptr),
    _left(nullptr)
{
}

Node::Node(Node::Operation operation, const Node *right, const Node *left):
    _operation(operation),
    _right(right),
    _left(left),
    _shape(nullptr)
{
}



bool Node::intersect(const Shape **shape, const Ray &ray, double *distance, Vector *normal) const
{
    if(isLeaf()) {
        *shape = _shape;
        return _shape->intersect(ray,distance,normal);
    }
    else {
        bool b1 = false, b2 = false;
        //        bool in1[1], in2[1];
        double d1[2],d2[2];
        Vector n1[2],n2[2];
        const Shape *s1, *s2;
        switch(_operation)
        {
        case Node::Union_NodeType:
            b2 = left()->intersect(&s2,ray,d2,n2);
            b1 = right()->intersect(&s1,ray,d1,n1);
            if(b1 || b2) {
                if(b1 && b2) {
                    if(d1[_IN]<d2[_IN]) {
                        *shape = s1;
                        distance[_IN] = d1[_IN];
                        distance[_OUT] = d2[_OUT];
                        normal[_IN] = n1[_IN];
                        normal[_OUT] = n2[_OUT];
                    }
                    else {
                        *shape = s2;
                        distance[_IN] = d2[_IN];
                        distance[_OUT] = d1[_OUT];
                        normal[_IN] = n2[_IN];
                        normal[_OUT] = n1[_OUT];
                    }
                }
                else {
                    if(b1) {
                        *shape = s1;
                        distance[_IN] = d1[_IN];
                        distance[_OUT] = d1[_OUT];
                        normal[_IN] = n1[_IN];
                        normal[_OUT] = n1[_OUT];
                    }
                    else {
                        *shape = s2;
                        distance[_IN] = d2[_IN];
                        distance[_OUT] = d2[_OUT];
                        normal[_IN] = n2[_IN];
                        normal[_OUT] = n2[_OUT];
                    }
                }
                return true;
            }
            else
                return false;
            break;
        case Node::Intersection_NodeType:
            b1 = right()->intersect(&s1,ray,d1,n1) && left()->inside(ray(d1[_IN]));
            b2 = left()->intersect(&s2,ray,d2,n2) && right()->inside(ray(d2[_IN]));
            if(b1 && b2) {
                if(d1[_IN]<d2[_IN]) {
                    *shape = s1;
                    distance[_IN] = d1[_IN];
                    distance[_OUT] = d2[_OUT];
                    normal[_IN] = n1[_IN];
                    normal[_OUT] = n2[_OUT];
                }
                else {
                    *shape = s2;
                    distance[_IN] = d2[_IN];
                    distance[_OUT] = d1[_OUT];
                    normal[_IN] = n2[_IN];
                    normal[_OUT] = n1[_OUT];
                }
                return true;
            }
            else if(b1)
            {
                *shape = s1;
                distance[_IN] = d1[_IN];
                distance[_OUT] = d2[_OUT];
                normal[_IN] = n1[_IN];
                normal[_OUT] = n2[_OUT];
            }
            else if(b2)
            {
                *shape = s2;
                distance[_IN] = d2[_IN];
                distance[_OUT] = d1[_OUT];
                normal[_IN] = n2[_IN];
                normal[_OUT] = n1[_OUT];
            }
            else
                return false;
            break;
        case Node::Substract_NodeType:
            b2 = left()->intersect(&s2,ray,d2,n2) && ((!right()->inside(ray(d2[_IN]))) || (!right()->inside(ray(d2[_OUT])))) ;
            b1 = right()->intersect(&s1,ray,d1,n1) && (left()->inside(ray(d1[_IN])) || left()->inside(ray(d1[_OUT])));
            if(b2)
            {
                *shape = s2;
                distance[_IN] = d2[_IN];
                distance[_OUT] = d1[_OUT];
                normal[_IN] = n2[_IN];
                normal[_OUT] = n1[_OUT];
            }
            else if(b1)
            {
                *shape = s1;
                distance[_IN] = d1[_IN];
                distance[_OUT] = d2[_OUT];
                normal[_IN] = n1[_IN];
                normal[_OUT] = n2[_OUT];
            }
            else
                return false;
            break;
        case Node::Shape_NodeType:
            break;
        }
    }
}

bool Node::inside(const Vector &dot) const
{
    switch(_operation)
    {
    case Node::Union_NodeType:
        return (_left->inside(dot) || _right->inside(dot));
    case Node::Intersection_NodeType:
        return (_left->inside(dot) && _right->inside(dot));
    case Node::Substract_NodeType:
        return (_left->inside(dot) && (!_right->inside(dot)));
    case Node::Shape_NodeType:
        return _shape->inside(dot);
    }
}

const Node *Node::right() const
{
    return _right;
}

const Node *Node::left() const
{
    return _left;
}

const Shape *Node::shape() const
{
    return _shape;
}

Node::Operation Node::operation() const
{
    return _operation;
}

void Node::setRight(const Node *value)
{
    if(_operation != Node::Shape_NodeType)
        _right = value;
    else
        throw std::logic_error(
                std::string(
                    QObject::tr("You can't set a child if the node is a shape.").toAscii().data()));
}

void Node::setLeft(const Node *value)
{
    if(_operation != Node::Shape_NodeType)
        _right = value;
    else
        throw std::logic_error(
                std::string(
                    QObject::tr("You can't set a child if the node is a shape.").toAscii().data()));
}

void Node::setOperation(Node::Operation operation)
{
    if(operation == Node::Shape_NodeType
            && _operation != Node::Shape_NodeType) {
        if(_shape != nullptr) {
            delete _right;
            delete _left;
            _operation = operation;
        }
        else
            throw std::logic_error(
                    std::string(
                        QObject::tr("You can't set an operation to Shape if the sahpe is not allready defined.").toAscii().data()));
    }
    else
        _operation = operation;
}

void Node::setShape(const Shape *value)
{
    if(_operation == Node::Shape_NodeType)
        _shape = value;
    else
        throw std::logic_error(
                std::string(
                    QObject::tr("You can't set the sahpe is Node is an operation.").toAscii().data()));
}

//                if(in2[1])
//                {

//...

//                }
//                else
//                {
//                    if(in1[1])
//                    {
//                        if(b1 && b2){
//                            if(d2[_IN] < d1[_IN]) {
//                                return false;
//                            }
//                            else {
//                                *shape = s1;
//                                distance[_IN] = d1[_IN];
//                                distance[_OUT] = d2[_IN];
//                                normal[_IN] = n1[_IN];
//                                normal[_OUT] = n2[_IN];
//                                return true;
//                            }
//                        }
//                    }
//                    else
//                    {
//                        if(b1 && b2){
//                            if(d2[_IN] < d1[_IN]) {
//                                *shape = s2;
//                                distance[_IN] = d2[_IN];
//                                distance[_OUT] = d2[_IN];
//                                normal[_IN] = n2[_IN];
//                                normal[_OUT] = n2[_IN];
//                                return true;
//                            }
//                            else {
//                                *shape = s1;
//                                distance[_IN] = d1[_IN];
//                                distance[_OUT] = d2[_IN];
//                                normal[_IN] = n1[_IN];
//                                normal[_OUT] = n2[_IN];
//                                return true;
//                            }
//                        }
//                        else{
//                            *shape = s2;
//                            distance[_IN] = d2[_IN];
//                            distance[_OUT] = d2[_IN];
//                            normal[_IN] = n2[_IN];
//                            normal[_OUT] = n2[_IN];
//                            return true;
//                        }
//                    }
//                }



//                    if(d1[_IN]<d2[_IN]) {
//                        *shape = s1;
//                        distance[_IN] = d1[_IN];
//                        distance[_OUT] = d2[_OUT];
//                        normal[_IN] = n1[_IN];
//                        normal[_OUT] = n2[_OUT];
//                    }
//                    else {
//                        *shape = s2;
//                        distance[_IN] = d2[_IN];
//                        distance[_OUT] = d1[_OUT];
//                        normal[_IN] = n2[_IN];
//                        normal[_OUT] = n1[_OUT];
//                    }

//                if(b1 && b2){
//                    if(d2[_IN] < d1[_IN]) {
//                        *shape = s2;
//                        distance[_IN] = d2[_IN];
//                        distance[_OUT] = d2[_OUT];
//                        normal[_IN] = n2[_IN];
//                        normal[_OUT] = n2[_OUT];
//                        return true;
//                    }
//                    else {
//                        if(d2[_OUT] < d1[_OUT])
//                            return false;
//                        else {
//                            *shape = s1;
//                            distance[_IN] = d1[_OUT];
//                            distance[_OUT] = d2[_OUT];
//                            normal[_IN] = n1[_OUT];
//                            normal[_OUT] = n2[_OUT];
//                            return true;
//                        }
//                    }
//                }
//                else{
//                    *shape = s2;
//                    distance[_IN] = d2[_IN];
//                    distance[_OUT] = d2[_OUT];
//                    normal[_IN] = n2[_IN];
//                    normal[_OUT] = n2[_OUT];
//                    return true;
//                }
